'use strict';

exports.__esModule = true;
exports.matches = exports.hasDOM = undefined;

const _typeof2 = require('babel-runtime/helpers/typeof');

const _typeof3 = _interopRequireDefault(_typeof2);

exports.hasClass = hasClass;
exports.addClass = addClass;
exports.removeClass = removeClass;
exports.toggleClass = toggleClass;
exports.getStyle = getStyle;
exports.setStyle = setStyle;
exports.scrollbar = scrollbar;
exports.getOffset = getOffset;
exports.getPixels = getPixels;
exports.getClosest = getClosest;
exports.getMatches = getMatches;

const _string = require('./string');

const _object = require('./object');

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj };
}

/**
 * 是否能使用 DOM 方法
 * @type {Boolean}
 */
const hasDOM = (exports.hasDOM =
  typeof window !== 'undefined' && !!window.document && !!document.createElement);

/**
 * 节点是否包含指定 className
 * @param  {Element}  node
 * @param  {String}  className
 * @return {Boolean}
 *
 * @example
 * dom.hasClass(document.body, 'foo');
 */
function hasClass(node, className) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return false;
  }

  if (node.classList) {
    return node.classList.contains(className);
  } else {
    return node.className.indexOf(className) > -1;
  }
}

/**
 * 添加 className
 * @param {Element} node
 * @param {String} className
 *
 * @example
 * dom.addClass(document.body, 'foo');
 */
function addClass(node, className, _force) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return;
  }

  if (node.classList) {
    node.classList.add(className);
  } else if (_force === true || !hasClass(node, className)) {
    node.className += ` ${className}`;
  }
}

/**
 * 移除 className
 * @param  {Element} node
 * @param  {String} className
 *
 * @example
 * dom.removeClass(document.body, 'foo');
 */
function removeClass(node, className, _force) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return;
  }

  if (node.classList) {
    node.classList.remove(className);
  } else if (_force === true || hasClass(node, className)) {
    node.className = node.className.replace(className, '').replace(/\s+/g, ' ').trim();
  }
}

/**
 * 切换 className
 * @param  {Element} node
 * @param  {String} className
 * @return {Boolean}           执行后节点上是否还有此 className
 *
 * @example
 * dom.toggleClass(document.body, 'foo');
 */
function toggleClass(node, className) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return false;
  }

  if (node.classList) {
    return node.classList.toggle(className);
  } else {
    const flag = hasClass(node, className);
    flag ? removeClass(node, className, true) : addClass(node, className, true);

    return !flag;
  }
}

/**
 * 元素是否匹配 CSS 选择器
 * @param  {Element} node       DOM 节点
 * @param  {String}  selector   CSS 选择器
 * @return {Boolean}
 *
 * @example
 * dom.matches(mountNode, '.container'); // boolean
 */
const matches = (exports.matches = (function () {
  let matchesFn = null;
  /* istanbul ignore else */
  if (hasDOM) {
    const _body = document.body || document.head;
    matchesFn = _body.matches
      ? 'matches'
      : _body.webkitMatchesSelector
      ? 'webkitMatchesSelector'
      : _body.msMatchesSelector
      ? 'msMatchesSelector'
      : _body.mozMatchesSelector
      ? 'mozMatchesSelector'
      : null;
  }

  return function (node, selector) {
    if (!hasDOM || !node) {
      return false;
    }

    return matchesFn ? node[matchesFn](selector) : false;
  };
})());

/**
 * 获取元素计算后的样式
 * @private
 * @param  {Element} node
 * @return {Object}
 */
function _getComputedStyle(node) {
  return node && node.nodeType === 1 ? window.getComputedStyle(node, null) : {};
}

const PIXEL_PATTERN = /margin|padding|width|height|max|min|offset|size|top/i;
const removePixel = { left: 1, top: 1, right: 1, bottom: 1 };

/**
 * 校验并修正元素的样式属性值
 * @private
 * @param  {Element} node
 * @param  {String} type
 * @param  {Number} value
 */
function _getStyleValue(node, type, value) {
  type = type.toLowerCase();

  if (value === 'auto') {
    if (type === 'height') {
      return node.offsetHeight || 0;
    }
    if (type === 'width') {
      return node.offsetWidth || 0;
    }
  }

  if (!(type in removePixel)) {
    // 属性值是否需要去掉 px 单位，这里假定此类的属性值都是 px 为单位的
    removePixel[type] = PIXEL_PATTERN.test(type);
  }

  return removePixel[type] ? parseFloat(value) || 0 : value;
}

const floatMap = { cssFloat: 1, styleFloat: 1, float: 1 };

/**
 * 获取元素计算后的样式
 * @param  {Element} node DOM 节点
 * @param  {String} name 属性名
 * @return {Number|Object}
 */
function getStyle(node, name) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return null;
  }

  const style = _getComputedStyle(node);

  // 如果不指定属性名，则返回全部值
  if (arguments.length === 1) {
    return style;
  }

  name = floatMap[name] ? ('cssFloat' in node.style ? 'cssFloat' : 'styleFloat') : name;

  return _getStyleValue(
    node,
    name,
    style.getPropertyValue((0, _string.hyphenate)(name)) ||
      node.style[(0, _string.camelcase)(name)],
  );
}

/**
 * 设置元素的样式
 * @param {Element} node  DOM 节点
 * @param {Object|String} name  属性名，或者是一个对象，包含多个属性
 * @param {Number|String} value 属性值
 *
 * @example
 * // 设置单个属性值
 * dom.setStyle(mountNode, 'width', 100);
 * // 设置多条属性值
 * dom.setStyle(mountNode, {
 *     width: 100,
 *     height: 200
 * });
 */
function setStyle(node, name, value) {
  /* istanbul ignore if */
  if (!hasDOM || !node) {
    return false;
  }

  // 批量设置多个值
  if (
    (typeof name === 'undefined' ? 'undefined' : (0, _typeof3.default)(name)) === 'object' &&
    arguments.length === 2
  ) {
    (0, _object.each)(name, (val, key) => {
      return setStyle(node, key, val);
    });
  } else {
    name = floatMap[name] ? ('cssFloat' in node.style ? 'cssFloat' : 'styleFloat') : name;
    if (typeof value === 'number' && PIXEL_PATTERN.test(name)) {
      value += 'px';
    }
    node.style[(0, _string.camelcase)(name)] = value; // IE8 support
  }
}

/**
 * 获取默认的滚动条大小
 * @return {Object} width, height
 */
function scrollbar() {
  const scrollDiv = document.createElement('div');
  scrollDiv.className += 'just-to-get-scrollbar-size';

  setStyle(scrollDiv, {
    position: 'absolute',
    width: '100px',
    height: '100px',
    overflow: 'scroll',
    top: '-9999px',
  });
  document.body && document.body.appendChild(scrollDiv);
  const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
  const scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight;
  document.body.removeChild(scrollDiv);

  return {
    width: scrollbarWidth,
    height: scrollbarHeight,
  };
}

/**
 * 获取元素距离视口顶部和左边的偏移距离
 * @return {Object} top, left
 */
function getOffset(node) {
  const rect = node.getBoundingClientRect();
  const win = node.ownerDocument.defaultView;
  return {
    top: rect.top + win.pageYOffset,
    left: rect.left + win.pageXOffset,
  };
}

/**
 * 获取不同单位转为 number 的长度
 * @param {string|number} len 传入的长度
 * @return {number} pixels
 */
function getPixels(len) {
  const win = document.defaultView;
  if (typeof +len === 'number' && !isNaN(+len)) {
    return +len;
  }

  if (typeof len === 'string') {
    const PX_REG = /(\d+)px/;
    const VH_REG = /(\d+)vh/;
    if (Array.isArray(len.match(PX_REG))) {
      return +len.match(PX_REG)[1] || 0;
    }

    if (Array.isArray(len.match(VH_REG))) {
      const _1vh = win.innerHeight / 100;
      return +(len.match(VH_REG)[1] * _1vh) || 0;
    }
  }

  return 0;
}

/**
 * 匹配特定选择器且离当前元素最近的祖先元素（也可以是当前元素本身），如果匹配不到，则返回 null
 * @param {element} dom 待匹配的元素
 * @param {string} selecotr 选择器
 * @return {element} parent
 */
function getClosest(dom, selector) {
  /* istanbul ignore if */
  if (!hasDOM || !dom) {
    return null;
  }

  // ie9
  /* istanbul ignore if */
  if (!Element.prototype.closest) {
    if (!document.documentElement.contains(dom)) return null;
    do {
      if (getMatches(dom, selector)) return dom;
      dom = dom.parentElement;
    } while (dom !== null);
  } else {
    return dom.closest(selector);
  }
  return null;
}

/**
 * 如果元素被指定的选择器字符串选择，getMatches()  方法返回true; 否则返回false
 * @param {element} dom 待匹配的元素
 * @param {string} selecotr 选择器
 * @return {element} parent
 */
function getMatches(dom, selector) {
  /* istanbul ignore if */
  if (!hasDOM || !dom) {
    return null;
  }

  /* istanbul ignore if */
  if (Element.prototype.matches) {
    return dom.matches(selector);
  } else if (Element.prototype.msMatchesSelector) {
    return dom.msMatchesSelector(selector);
  } else if (Element.prototype.webkitMatchesSelector) {
    return dom.webkitMatchesSelector(selector);
  }

  return null;
}
